using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class SyntaxErrorLoggerSpecification {
        private SyntaxErrorLogger Logger;

        [SetUp]
        public void SetUp () {
            Logger = new SyntaxErrorLogger ();
        }

        [Test]
        public void ShouldCommitSyntaxError () {
            IErrorInformation production = LogSyntaxError (3);

            AssertSyntaxError (0, 3, production);
        }

        [Test]
        public void SecondErrorWithSameIndexShouldProduceSyntaxErrorWithBothProductions () {
            IErrorInformation firstProduction = LogSyntaxError (3);
            IErrorInformation secondProduction = LogSyntaxError (3);

            AssertSyntaxError (0, 3, firstProduction, secondProduction);
        }

        [Test]
        public void SecondErrorWithLesserIndexShouldNotBeAdded () {
            IErrorInformation firstProduction = LogSyntaxError (3);
            LogSyntaxError (2);

            AssertSyntaxError (0, 3, firstProduction);
        }

        [Test]
        public void SecondErrorWithGreaterIndexShouldReplaceFirst () {
            LogSyntaxError (3);
            IErrorInformation secondProduction = LogSyntaxError (4);

            AssertSyntaxError (0, 4, secondProduction);
        }

        [Test]
        public void NoErrorsShouldProduceNoSyntaxErrors () {
            AssertNoErrors ();
        }

        private void AssertNoErrors () {
            Logger.CommitError (null);
            Assert.That (Logger.SyntaxErrors.Count (), Is.EqualTo (0));
        }

        [Test]
        public void ShouldNotLogProductionIfNotIncludedInErrors () {
            var production = new Mock<IProduction> ();
            production.SetupGet (p => p.IsIncludedInErrors).Returns (true);
            production.SetupGet (p => p.ErrorInformation).Returns ((IErrorInformation) null);
            Logger.LogSyntaxError (0, production.Object, new ParseContext());
            AssertNoErrors ();
        }

        [Test]
        public void ShouldSetErrorForAllSyntaxErrorsAndNullForPathsTo () {
            IProduction production1 = CreateProduction ();
            IProduction production2 = CreateProduction ();
            var context = new ParseContext();

            Logger.LogSyntaxError (3, CreateProduction (), new ParseContext());
            Logger.LogSyntaxError (4, production1, context,
                                   () => new[] {new ProductionInvocation (production1, 4), new ProductionInvocation (production2, 2)});

            var production3 = CreateProduction ();
            var production4 = CreateProduction ();
            Logger.LogSyntaxError (4, production3, new ParseContext(),
                () => new[] {new ProductionInvocation(production3, 4), new ProductionInvocation(production4, 2)});

            Logger.SetErrorInMemoTable (null);

            Assert.That (production1.GetMemoTable (null) [4], Is.SameAs (Logger.ErrorResult));
            Assert.False (production2.GetMemoTable (null).ContainsKey (2));

            Assert.False (production3.GetMemoTable (null).ContainsKey (4));
            Assert.False (production4.GetMemoTable (null).ContainsKey (2));
        }

        [Test]
        public void ShouldSetRecoveryResultsForEachErrorRecovery () {
            var result1 = new ParseResult (0, null, null);
            IProduction production1 = CreateProduction ();
            Logger.AddErrorRecovery (production1, 5, result1);

            var result2 = new ParseResult (0, null, null);
            IProduction production2 = CreateProduction ();
            Logger.AddErrorRecovery (production2, 3, result2);

            Logger.SetErrorInMemoTable (null);

            Assert.That (production1.GetMemoTable(null)[5], Is.SameAs (result1));
            Assert.That (production2.GetMemoTable(null)[3], Is.SameAs (result2));
        }

        [Test]
        public void ShouldReportHavingErrorsCorrectly () {
            Assert.False (Logger.HasLoggedErrors);

            LogSyntaxError (4);

            Assert.True (Logger.HasLoggedErrors);
        }

        [Test]
        public void ShouldNotLogErrorsWhileInRecovery () {
            IErrorInformation p = LogSyntaxError (3);
            Logger.BeginRecovery ();
            IErrorInformation p2 = LogSyntaxError (3);

            AssertSyntaxError (0, 3, p);

            IErrorInformation p3 = LogSyntaxError (5);

            AssertSyntaxError (1, 5, p3);
        }

        [Test]
        public void CommitShouldNotCommitSameErrorTwice () {
            IErrorInformation p = LogSyntaxError (3);

            Logger.CommitError (null);
            Logger.CommitError (null);

            Assert.That (Logger.SyntaxErrors.Count (), Is.EqualTo (1));
        }

        private void AssertSyntaxError (int errorIndex, int sourceIndex, params IErrorInformation [] productions) {
            Logger.CommitError (null);
            SyntaxError syntaxError = Logger.SyntaxErrors.ElementAt (errorIndex);
            Assert.That (syntaxError.Index, Is.EqualTo (sourceIndex));
            Assert.That (syntaxError.ExpectedProductions, Is.EquivalentTo (productions));
        }

        private IErrorInformation LogSyntaxError (int index) {
            IProduction production = CreateProduction ();
            Logger.LogSyntaxError (index, production, new ParseContext());
            return production.ErrorInformation;
        }

        private static IProduction CreateProduction () {
            var production = new Mock<IProduction> ();
            production.SetupGet (p => p.ErrorInformation).Returns (new Mock<IErrorInformation> ().Object);
            production.Setup (p => p.GetMemoTable (It.IsAny<char[]> ())).Returns (new Dictionary<int, ParseResult> ());
            return production.Object;
        }
    }
}